package com.killLog.common;


import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.killLog.dao.TokenDao;
import com.killLog.exception.NoAuthenticateException;
import com.killLog.exception.SystemException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Date;


/**
令牌工具类
*/
@Component//将类交给spring容器管理（@Controller、@Service等是该注解的衍生注解）
@Slf4j
//生成token 然后写入redis
public class TokenUtils {
    private static final String CLAIM_NAME_USERID="userId";//令牌中存放用户账号对应的名字
    private static final String CLAIM_NAME_USERNAME="userName";//令牌中存放用户名对应的名字
    private static Integer expireTime;//配置文件里面设置的令牌过期时间
    private static StringRedisTemplate stringRedisTemplate;//redis操作字符串的便捷工具对象
    @Autowired//通过set方法注入stringRedisTemplate对象
    public static void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        TokenUtils.stringRedisTemplate = stringRedisTemplate;
    }

    public void setEnv(Environment env){//获取yml配置文件里面配置的令牌过期时间
        try {
            expireTime = env.getProperty("PSY_Token_Time.expire-time", Integer.class);
            log.info("expireTime>>令牌过期时间⏱️:{}",expireTime);
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("从配置文件获取令牌过期时间失败！默认令牌过期时间为60s");
            expireTime = 60;
        }
    }
    /**
    生成token字符串的方法
    */
    public static String sign(CurrentUser currentUser,String securityKey){
        Algorithm algorithm = Algorithm.HMAC256(securityKey);//1.创建一个加密算法对象
        String token = JWT.create()
                .withClaim(CLAIM_NAME_USERID, currentUser.getUserId())
                .withClaim(CLAIM_NAME_USERNAME, currentUser.getUserName())
                .withExpiresAt(new Date(System.currentTimeMillis() + expireTime * 1000))
                .sign(algorithm);//2.生成混合登录用户id、name、有效登录时长、加密、的token
        return token;//3.返回生成的token
    }
    /**
    将当前用户信息以用户密码为密钥生成一个token并写入redis
    */
    public static String loginSign(CurrentUser currentUser,String password){
        String token = sign(currentUser, password);
        //将令牌存入redis中，并设置redis中的令牌过期时间为令牌本身过期时间的2倍
        stringRedisTemplate.opsForValue().set(token,token,expireTime*2);
        return token;
    }
    /**
    从客户端传来的令牌里面获取当前用户
    */
    public static CurrentUser getCurrentUser(String clientToken){
        DecodedJWT decodedJWT=null;//存储解析后的令牌
        //1.传来的令牌为空
        if (!StringUtils.hasText(clientToken)){
            throw  new NoAuthenticateException("令牌为空！请登录！");
        }
        //2.传来的令牌不为空，解析令牌（解析过程不出错就说明令牌正确）
        try {
            decodedJWT = JWT.decode(clientToken);//获取解码后的令牌
        } catch (JWTDecodeException e) {
            throw new NoAuthenticateException("令牌格式错误！请登录！");
        }
        //3.从解析后的令牌中获取当前用户信息
        String userId = decodedJWT.getClaim(CLAIM_NAME_USERID).asString();//获取用户账号
        String userName = decodedJWT.getClaim(CLAIM_NAME_USERNAME).asString();//获取用户名
        if (!StringUtils.hasText(userId) || !StringUtils.hasText(userName)){
            throw new NoAuthenticateException("令牌缺失用户信息！请登录！");
        }
        //4.通过上面的几个判断之后，传回正确的token
        return new CurrentUser(userId,userName);
    }
    /**
    判断token中的密码是否正确，如果正确再判断令牌是否过期
    */
    private static TokenDao tokenDao;//引入这个接口
    @Autowired
    public static void setTokenDao(TokenDao tokenDao) {//static修饰的话得使用set+@Autowired进行依赖注入
        TokenUtils.tokenDao = tokenDao;
    }
    public static void verify(String clientToken){
        //1.从令牌中获取当前用户信息
        CurrentUser currentUser = getCurrentUser(clientToken);
        //2.根据当前用户账号从数据库中对密钥进行校验
        String password=null;
        try {
            password = tokenDao.findPasswordByUserId(currentUser.getUserId());
        } catch (Exception e) {
            log.error("系统错误：",e);
            throw new SystemException("系统错误");
        }
        //3.非空判断
        if (!StringUtils.hasText(password)){
            throw new NoAuthenticateException("没有找到当前用户密码，请登录！");
        }
        //4.验证令牌：验证redis中存储的令牌
        String catchToken = stringRedisTemplate.opsForValue().get(clientToken);//获取redis中存储的token
        if (!StringUtils.hasText(catchToken)){
            throw new NoAuthenticateException("令牌过期！请重新登录！");
        }
        //TODO 5.根据密钥，获取对应的算法
    }
    /**
    从redis中移除令牌（推出登录时用）
    */
    public static void removeToken(String clientToken){
        stringRedisTemplate.delete(clientToken);
    }
    
}
